(function($){
	$.fn.fsBuildClouds = function (inSettings){
		var defaultSettings = {
				fadeOutTime : 5000
		};
		var settings = $.extend({}, defaultSettings, inSettings || {});

		return this.each(function(){
			new Cloud($(this), settings);
		});
	}

	var Cloud = function (cloud, settings){
		var width = cloud.width();
		var height = cloud.height();
		var cloudCoord = {left : cloud.position().left, top : cloud.position().top};

		var startCoord = {x : Math.round(width/2), y : Math.round(height/2)};
		var X = 0, Y = 1, Z = 2;
		var TIMER_INTERVAL = 50;

		//coordinates initialization
		var coordinates = [];
		var R = width < height ? width/2 : height/2; 
		$.each(settings.data, function(index){
			if(getCoordType(this.coords) == "cartesian"){
				coordinates[index] = $.extend({}, this.coords);
				//normilize coords
				for (i in coordinates[index]){
					coordinates[index][i] = coordinates[index][i] / R;
				}
			}else if(getCoordType(this.coords) == "spherical"){
				//transform spherical to cartesian coords with normalized radius
				coordinates[index] = new Object();
				coordinates[index].x = (this.coords.r / R) * Math.cos(this.coords.t)*Math.cos(this.coords.f);
				coordinates[index].y = (this.coords.r / R) * Math.cos(this.coords.t)*Math.sin(this.coords.f);
				coordinates[index].z = (this.coords.r / R) * Math.sin(this.coords.t);
			}else{
				var minus = Math.random(1) > 0.5 ? 1 : -1;
				coordinates[index] = new Object();
				coordinates[index].x = minus*Math.random(1);
				minus = Math.random(1) > 0.5 ? 1 : -1;
				coordinates[index].y = minus*Math.random(1)*(Math.sqrt(1 - coordinates[index].x*coordinates[index].x));
				minus = Math.random(1) > 0.5 ? 1 : -1;
				coordinates[index].z = minus*Math.sqrt(1 - coordinates[index].x*coordinates[index].x - coordinates[index].y*coordinates[index].y);
			}
		});

		//put tags into cloud
		for(e in settings.data){
			var div = $("<div id=\'"+"item"+e+"\'>");
			cloud.append(div);
			if(settings.data[e].url != undefined){
				$("<a href=\""+settings.data[e].url+"\">" + settings.data[e].text + "</a>").appendTo(div);
			}else{
				div.append(settings.data[e].text);
			}
		}

		function getCoordType(coord){
			if(coord != undefined){
				if(coord.x != undefined && coord.y != undefined && coord.z != undefined){
					return "cartesian";
				}else if(coord.f != undefined && coord.t != undefined && coord.r != undefined){
					return "spherical";
				}
			}
			return "undefined";
		}

		//transpositioned rotation matrix
		var matrix =	[[[1,0,0],
						[0,Math.cos,Math.sin],
						[0,function(a){return -1*Math.sin(a)},Math.cos]],

						[[Math.cos,0,function(a){return -1*Math.sin(a)}],
						[0,1,0],
						[Math.sin,0,Math.cos]],

						[[Math.cos,Math.sin,0],
						[function(a){return -1*Math.sin(a)},Math.cos,0],
						[0,0,1]]];

		var angle = {alfa : 0, betta : 0, gamma : 0};
		var validateTimer;
		var stopTimer;
		var stopTimerCount = 0;
		var reduceRate = {alfa : 0, betta : 0};
		validate();

		//event handlers
		cloud.mousemove(function(evt){
			//angle should depends from distance between center of div and mouse position
			angle.alfa = ((evt.clientX - cloudCoord.left) - startCoord.x)/(startCoord.x*10);
			angle.betta = (startCoord.y - (evt.clientY - cloudCoord.top))/(startCoord.y*10);
		}).hover(function(event){ //mouseenter 
			//when mouse enters to current div area we need to clear all timers and start again
			window.clearInterval(stopTimer);
			window.clearInterval(validateTimer);
			setTimer(TIMER_INTERVAL);
		}, function(event){ //mouseleave 
			//when mouse  leave current div area we need to reduce rotation angles
			if(settings.fadeOutTime > TIMER_INTERVAL){
				stopTimerCount = 0;
				reduceRate.alfa = angle.alfa / (settings.fadeOutTime/TIMER_INTERVAL);
				reduceRate.betta = angle.betta / (settings.fadeOutTime/TIMER_INTERVAL);
				console.log(reduceRate.alfa + " " + reduceRate.betta);
				stopTimer = window.setInterval(reduceAngle, TIMER_INTERVAL);
			}else{
				window.clearInterval(validateTimer);
			}
		});

		//function that reduce rotation angles acordingly with fadeOutTime
		function reduceAngle(){
			stopTimerCount++;
			if(stopTimerCount <= settings.fadeOutTime/TIMER_INTERVAL){
					angle.alfa = angle.alfa - reduceRate.alfa;
					angle.betta = angle.betta - reduceRate.betta;
			}else{
					window.clearInterval(validateTimer);
					window.clearInterval(stopTimer);
			}
		};

		//validate label's postion inside div
		function validate(){
			cloud.children("div").each(function(index){
				var cloud = settings.data[index];
				var size =	(coordinates[index].z + 1/4)	/* 1/4 - z coord affection to size*/ + 
							(cloud.rate/100)*2	/* 2 - rate affection to size*/ + 
							"em";
				$(this).css({	top : startCoord.y + coordinates[index].y*Math.round(0.45*height) - Math.round($(this).height()/2) + "px",
								left : startCoord.x + coordinates[index].x*Math.round(0.45*width) - Math.round($(this).width()/2) + "px",
								opacity : (coordinates[index].z + 1)/2 + 0.3/*0.3 - opacity of very back element*/,
								fontSize : size});
			});
		};

		function setTimer(timeOut){
			validateTimer =	window.setInterval(function(){
							rotate(X, angle.betta);
							rotate(Y, angle.alfa);
							validate();
						}, timeOut);
		};

		/*
		* nAxis	=	0 rotation around X-Axis, 
		*			1 rotation around Y-Axis,
		*			2 rotation around Z-Axis, 
		* angle - rotation angle
		*/
		function rotate(nAxis, angle){
			for(i in settings.data){
				var coords = [coordinates[i].x, coordinates[i].y, coordinates[i].z];
				var newCoords = new Array(3);
				for(row in matrix[nAxis]){
					newCoords[row] = new Number(0);
					for(col in matrix[nAxis][row]){
						if(typeof(matrix[nAxis][row][col]) == 'number'){
							newCoords[row] += coords[col] * matrix[nAxis][row][col];
						}else if(typeof(matrix[nAxis][row][col]) == 'function'){
							newCoords[row] += coords[col] * matrix[nAxis][row][col](angle);
						}
					}
				}
				coordinates[i].x = newCoords[X];
				coordinates[i].y = newCoords[Y];
				coordinates[i].z = newCoords[Z];
			}
		};
	};
})(jQuery);
